#include <iostream>
#include <map>
#include <cstring>
#include <cstdarg>
#include <sys/stat.h>
#include <iomanip>
#include <filesystem>
#include <lvos.h>
#include <utils.h>
#include <log.h>
#include <keyMap.h>
#define DEFAULT_LOG_PATH "log"
#define DEFAULT_LOG_FILE "message"

std::string LogFile::getLogName()
{
    return m_fileName;
}
LogFile::LogFile(const char * file, int fileCnt, int fileSize, int level)
{
    if (file == nullptr) file = DEFAULT_LOG_FILE;
    m_fileName = utils::format("%s/%s", DEFAULT_LOG_PATH, file);
    m_fileCnt = std::max(fileCnt, 1);
    m_fileSzie = std::max(fileSize, 1) * (1024 * 1024); // MB
    m_logLevel = level;
    utils::mkdirPath(DEFAULT_LOG_PATH);
    m_file = nullptr;
    reOpen();
    setLogTraceDebug(false);
}
LogFile::~LogFile()
{
    std::lock_guard<std::mutex> lock{m_lock};
    if (m_file) fclose(m_file);
    m_file = nullptr;
}

void LogFile::formatDateTime(std::ostringstream& oss)
{
    std::tm local_tm;
    const auto now = std::chrono::system_clock::now();
    const std::time_t now_sec = std::chrono::system_clock::to_time_t(now);
    const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;

    utils::localtime_rs(&local_tm, &now_sec);

    oss << std::put_time(&local_tm, "%Y-%m-%d %H:%M:%S");
    oss << "." << std::setw(3) << std::setfill('0') << ms.count();
}

void LogFile::formatFileInfo(std::ostringstream& oss, int level,
    const char* file,const char* func,int line)
{
    auto fileNameGet = [](const char* file) -> std::string
        {
            if (!file || (*file == EOFS))
            {
                return NULLSTR;
            }

            std::filesystem::path filePath(file);

            return filePath.filename().string();
        };
#ifdef _DEBUG
    oss << utils::format(" [%-5s] [%s:%d] [%s] ",
        log_to_string(level), fileNameGet(file).c_str(), line, func);
#else
    oss << utils::format(" [%-5s] [%s:%d] ",
        log_to_string(level), fileNameGet(file).c_str(), line);
#endif // DEBUG

}
size_t LogFile::write(std::ostringstream& oss)
{
    const std::string& logMessage = oss.str();
    if (!logMessage.empty() && (logMessage.back() != ENDL))
    {
        oss << std::endl;
    }

    return write(oss.str());
}

size_t LogFile::write(const std::string& outString)
{
    if (getLogTraceDebug()) LVOS_Write(outString, STDOUT_FILENO);
    return write(outString.c_str(), outString.size());
}

/*
 * rotate the log file xxx.log.n-1 => xxx.log.n, and xxx.log => xxx.log.0
 */
void LogFile::file_rotate(void)
{
    /* mv xxx.log.n-1 => xxx.log.n, and xxx.log => xxx.log.0 */
    std::lock_guard<std::mutex> lock{ m_lock };
    for (int n = m_fileCnt - 1; n >= 0; --n)
    {
        std::string oldpath = m_fileName;
        if (n > 0)
        {
            oldpath += utils::format(".%d", n - 1);
        }
        std::string newpath = utils::format("%s.%d", m_fileName.c_str(), n);
        remove(newpath.c_str());
        (void) rename(oldpath.c_str(), newpath.c_str());
    }
}

/*
 * Check if it needed retate
 */
bool LogFile::file_retate_check(void)
{
    struct stat statbuf;
    statbuf.st_size = 0;
    if (stat(m_fileName.c_str(), &statbuf) < 0)
    {
        reOpen();
        return false;
    }

    COND_CHECK_RET_VAL(statbuf.st_size > m_fileSzie, true);

    return false;
}
inline void LogFile::close()
{
    std::lock_guard<std::mutex> lock{m_lock};
    if (m_file)
        fclose(m_file);
    m_file = nullptr;
}
inline void LogFile::reOpen()
{
    close();
    m_file = fopen(m_fileName.c_str(), "at+");
    
    COND_CHECK_RET_VAL(m_file == nullptr, );

    setvbuf(m_file, NULL, _IOLBF, 2);
}
size_t LogFile::write(const char* buf, size_t len)
{
    size_t retVal = 0;

    {
        std::lock_guard<std::mutex> lock{m_lock};
        if (m_file)
        {
            retVal = fwrite(buf, 1, len, m_file);
        }
    }

    if (retVal <= 0)
    {
        utils::mkdirPath(DEFAULT_LOG_PATH);
        reOpen();
    }
    else if (file_retate_check())
    {
        close();
        file_rotate();
        reOpen();
    }
    else
    {
    }

    return retVal;
}
void LogFile::hexdump(const char *name, uint8_t width,
                      const char *buf, uint16_t size)
{
    auto is_print = [](char ch)
    {
        return (static_cast<unsigned int>(ch - SPACE) < 127u - SPACE);
    };

    width = ROUND_UP_UL(width, 8);

    for (uint16_t line_start = 0; line_start < size; line_start += width)
    {
        std::ostringstream oss;

        const int line_end = std::min(line_start + width - 1, size - 1);
        oss << name << " HEX: "
           << std::setw(4) << std::setfill('0') << std::hex << line_start << "-"
           << std::setw(4) << std::setfill('0') << std::hex << line_end << ": ";

        std::string ascii_str;
        for (uint8_t i = 0; i < width; ++i)
        {
            const uint16_t pos = line_start + i;
            if (pos >= size)
            {
                break;
            }
            oss << std::setw(2) << std::setfill('0') << std::hex
                << static_cast<uint32_t>(static_cast<uint8_t>(buf[pos])) << SPACES;
            ascii_str += is_print(buf[pos]) ? buf[pos] : '.';
        }

        oss << SPACES << ascii_str << std::endl;

        const std::string &result = oss.str();
        write(result.c_str(), result.size());
    }
}

bool LogFile::getLogTraceDebug()
{
    return m_debug;
}
STATUS LogFile::setLogTraceDebug(bool value)
{
    m_debug = value;
    return OK;
}
int LogFile::getLogLevel()
{
    return m_logLevel;
}

STATUS LogFile::setLogLevel(int level)
{
    m_logLevel = level;
    return OK;
}
